/**
 * 
 */
package com.pxingg.model.pubsub;

import java.util.LinkedList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONException;
import org.json.JSONObject;

import com.pxingg.Global;
import com.pxingg.datetime.Timer;
import com.pxingg.exception.XException;

/**
 * 消息 发布-订阅模型中的消息，携带消息数据，并且在消息交换中心的调度下，实现对消息的处理
 * 
 * @author pxingg
 * 
 */
public class Message
{
    /**
     * The log of this class
     */
    static Log log = LogFactory.getLog(Message.class);

    /**
     * 标记一个订阅者处理这个消息成功
     */
    public static final String RESULT_SUCCESS = "success";

    /**
     * 标记一个订阅者处理这个消息失败
     */
    public static final String RESULT_FAILURE = "failure";
    /**
     * 标记一个订阅者处理这个消息失败
     */
    public static final String RESULT_TIMEOUT = "timeout";

    /**
     * 标记一个订阅者处理这个消息的结果未知，没有这只result这个变量的值
     */
    public static final String RESULT_UNDEFINED = "undefined";

    /**
     * 默认的消息拥有者
     */
    public static final String MESSAGE_OWNER_UNDEFINED = "undefined";

    /**
     * 在创建新消息时，counter会加1，所以根据这个counter可以创建出一个唯一的消息
     */
    static protected long counter = 0;

    /**
     * 所有的数据都保存到这个JSON的数据结构中<br>
     * 对应的结构为 key : value
     */
    protected JSONObject data = new JSONObject();

    /**
     * 如果消息需要返回,那么把返回结构保存在这里<br>
     * 每个消息的订阅者组成一个JSON结构的执行结果，保存在这里
     */
    protected List<JSONObject> result = new LinkedList<JSONObject>();

    /**
     * 在创建消息时启动这个计数器，可以通过这个计数器来获取消息的创建时间
     */
    protected Timer timer = new Timer();

    /**
     * 对应创建消息是counter的值，来唯一标记这个消息
     */
    protected String messageID;

    /**
     * 消息的名字
     */
    protected String messageName;

    /**
     * 创建该消息的对象的名字
     */
    protected String messageOwner;

    /**
     * 消息的发布点
     */
    protected String publishPoint;

    /**
     * 获取消息超时的时间，单位微秒<br>
     * 如果消息执行时的时间超过这个时间，消息将不会再执行
     */
    protected int timeout = 0;

    /**
     * 构造消息的默认函数
     * 
     * @param messageName
     *            发布的消息的名字， 同时也是发布点的名字
     */
    public Message(final String messageName)
    {
        this(messageName, Message.MESSAGE_OWNER_UNDEFINED, messageName);
    }

    /**
     * 构造消息的默认函数, 消息名同时也是发布点的名字
     * 
     * @param messageName
     *            发布的消息的名字， 同时也是发布点的名字
     * @param messageOwner
     *            发布这个消息的对象的名字
     */
    public Message(final String messageName, final String messageOwner)
    {
        this(messageName, messageOwner, messageName);
    }

    /**
     * 消息的构造函数
     * 
     * @param messageName
     *            发布的消息的名字
     * @param messageOwner
     *            发布这个消息的对象的名字
     * @param publishPoint
     *            这个消息的发布点
     */
    public Message(final String messageName, final String messageOwner, final String publishPoint)
    {
        synchronized (Message.log)
        {
            // 这里加上锁，避免两个消息具有相同的消息ID
            messageID = String.format("%010d", ++Message.counter);
        }
        this.messageName = messageName;
        this.messageOwner = messageOwner;
        this.publishPoint = publishPoint;

        // 开始对这条消息进行计时
        timer.setName(messageName);
        timer.start();
    }

    /**
     * 确认消息中，是否包含某一个字段
     * 
     * @param key
     *            需要检测的字段名
     * @return true: 消息中包含这个字段<br>
     *         false: 消息中不包含这个字段
     */
    public boolean containsKey(final String key)
    {
        return data.has(key);
    }

    /**
     * 获取当前的执行结果的节点，用来保存一个订阅者对这个消息的处理结果
     * 
     * @return 保存执行结果的节点，为一个JSON对象
     */
    public JSONObject getCurrentResultContainer()
    {
        if (result.size() == 0)
        {
            // 在结果列表大小为0时，既没有订阅者处理这个消息时，生成一个临时的JSON对象来避免出错
            return new JSONObject();
        }

        return result.get(result.size() - 1);
    }

    /**
     * 获取消息的参数
     * 
     * @return 消息的参数
     */
    public JSONObject getData()
    {
        return data;
    }

    /**
     * 获取消息中的字段的值, 并把这个字段的值给转换成一个字符串<br>
     * 如果原来消息中没有这个字段，那么就会返回空字符串
     * 
     * @param key
     *            需要获取值的字段名
     * @return 获取的字段的值
     */
    public String getData(final String key)
    {
        final String value = data.optString(key, "");

        return value;
    }

    /**
     * 获取消息的ID
     * 
     * @return 消息的ID
     */
    public String getMessageID()
    {
        return messageID;
    }

    /**
     * 获取消息中的发布者
     * 
     * @return 消息的发布者
     */
    public String getMessageName()
    {
        return messageName;
    }

    /**
     * 获取消息的创建者
     * 
     * @return 消息的创建者
     */
    public String getMessageOwner()
    {
        return messageOwner;
    }

    /**
     * 新增加一个保存执行结果的节点，用来保存一个订阅者对这个消息的处理结果
     * 
     * @param subscriberPoint
     *            处理这个消息的订阅点
     * @param subscriber
     *            处理这个消息的订阅者的名字
     * @return 保持执行结果的节点，为一个JSON对象
     */
    public JSONObject getNewResultContainer(final String subscriberPoint, final String subscriber)
    {
        final JSONObject json = new JSONObject();
        try
        {
            json.put("subscriber", subscriber);
            json.put("subscriberPoint", subscriberPoint);
            json.put("result", Message.RESULT_UNDEFINED);
        }
        catch (final JSONException e)
        {
            Message.log.error(XException.getStack(e));
            Message.log.error("failed to get new result container for subscriber " + subscriber);
        }

        result.add(json);

        return json;
    }

    /**
     * 获取消息的发布点
     * 
     * @return 消息的发布点
     */
    public String getPublishPoint()
    {
        return publishPoint;
    }

    /**
     * 返回scriber执行消息结果的列表<br>
     * 由发布这个消息程序来获取结果
     * 
     * @return 消息的执行结构的列表
     */
    public List<JSONObject> getResult()
    {
        return result;
    }

    /**
     * @return 过去该消息被订阅者处理的次数
     */
    public int getSubscriberCounter()
    {
        return result.size();
    }

    /**
     * 获取消息的处理超时时间
     * 
     * @return 消息的处理超时时间
     */
    public int getTimeout()
    {
        return timeout;
    }

    /**
     * 获取消息总的计时器
     * 
     * @return 消息中的计时器
     */
    public Timer getTimer()
    {
        return timer;
    }

    /**
     * 因为消息的run函数执行完毕后会关闭计时器，所以可以使用计时器的关闭来判断消息是否发布完毕
     * 
     * @return true: 计时器停止，消息已经发布完毕 <br>
     *         false: 计时器还在计时，消息没有发布完毕
     * @return
     */
    public boolean isFinishPublish()
    {
        return timer.isStoped();
    }

    /**
     * 查看消息是否超时
     * 
     * @return true: 消息超时，将不再处理<br>
     *         false: 消息没有超时，会按照正常流程处理
     */
    public boolean isTimeOut()
    {
        if (timeout != 0 && timer.getInterval() > timeout)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    /**
     * 发布这个消息，提交给消息中心启动一个新的线程来执行
     */
    public void publish()
    {
        Global.switchCenter.publish(this);
    }

    /**
     * 发布这个消息，并使用当前这个线程来执行这个消息
     */
    public void publishAtOnce()
    {
        Global.switchCenter.processMessage(this);
    }

    /**
     * 设置订阅者处理当前消息的结果为失败
     */
    public void setCurrentResultFailure()
    {
        setResult("result", Message.RESULT_FAILURE);
    }

    /**
     * 设置当前订阅者处理消息的结果为成功
     */
    public void setCurrentResultSuccess()
    {
        setResult("result", Message.RESULT_SUCCESS);
    }

    /**
     * 设置当前订阅者处理消息的结果为超时
     */
    public void setCurrentResultTimeout()
    {
        setResult("result", Message.RESULT_TIMEOUT);
    }

    /**
     * 向消息中增加一个字段和String类型的值<br>
     * 如果原来消息中有这个字段，那么它原来的值将会被覆盖
     * 
     * @param key
     *            新增的消息字段名
     * @param value
     *            新增的消息字段的值
     * @return 消息本身，这样可以写一个串行的表达式
     */
    public Message setData(final String key, final String value)
    {
        try
        {
            data.put(key, value);
        }
        catch (final JSONException e)
        {
            Message.log.error(XException.getStack(e));
            Message.log.error("failed to set value for message " + messageID + ", value: " + key + " : " + value);
        }

        return this;
    }

    /**
     * 把执行结果保存在当前消息的map中<br>
     * 这个函数由监听这个消息的Subscriber调用
     * 
     * @param key
     *            字段名
     * @param value
     *            字段值
     */
    public void setResult(final String key, final String value)
    {
        if (result.size() == 0)
        {
            return;
        }

        try
        {
            getCurrentResultContainer().put(key, value);
        }
        catch (final JSONException e)
        {
            Message.log.error(XException.getStack(e));
            Message.log.error("failed to set result value for message " + messageID + ", value: " + key + " : " + value);
        }
    }

    /**
     * 设置消息的处理超时时间
     * 
     * @param timeout
     *            消息的处理超时时间
     */
    public void setTimeout(final int timeout)
    {
        this.timeout = timeout;
    }

    @Override
    public String toString()
    {
        final StringBuffer sb = new StringBuffer();
        String format = "%-12s = %s\n";
        sb.append(String.format(format, "messageID", messageID));
        sb.append(String.format(format, "messageName", messageName));
        sb.append(String.format(format, "messageOwner", messageOwner));
        sb.append(String.format(format, "publishPoint", publishPoint));
        sb.append(String.format(format, "Timer", timer));
        sb.append("data         = ");
        sb.append(data.toString());

        format = "%-12s = ";
        for (int index = 0, lastIndex = result.size() - 1; index <= lastIndex; index++)
        {
            final JSONObject json = result.get(index);
            if (index == 0)
            {
                sb.append("\n");
            }

            final String resultName = "result[" + index + "]";
            sb.append(String.format(format, resultName));
            sb.append(json.toString());
            if (index != lastIndex)
            {
                // 最后一个字段后面不增加回车，避免日志记录出现空行
                sb.append("\n");
            }
        }

        return sb.toString();
    }
}
